################################################################################
# IMPORTS
################################################################################

import unicodedata
import re

from django.conf import settings

################################################################################
# FUNCTION slugify
################################################################################

def slugify(str):
    """
    Returns a slugified version of str

    @str: the string to slugify
    """
    slug = unicode(str)
    slug = unicodedata.normalize('NFKD', slug).encode('ascii', 'ignore')
    slug = unicode(re.sub('[^\w\s-]', '', slug).strip().lower())
    slug = unicode(re.sub('\s', '-', slug).strip().lower())
    return slug

################################################################################
# FUNCTION slugify
################################################################################

def get_normalized_language():
    """
    Returns a language code that IS in settings.LANGUAGES. If no match can
    be found, fallback to 'en'
    """
    lang = settings.LANGUAGE_CODE
    codes = [item[0] for item in settings.LANGUAGES]
    if lang in codes:
        return lang
    if '-' in lang:
        lang = lang.split('-')[0]
        return lang if lang in codes else 'en'
    return 'en'

################################################################################
# FUNCTION get_related_model_instances
################################################################################

def get_related_model_instances(term):
    """
    go through all registered Model and find all model that has a term
    relation field (foreign key or many to many fields) and return all
    model that are in relation with specified term.

    @term: the term that is used to look for model instances.
    """
    instances = []
    models = []
    # Retrieve all registed models.
    for app_name, models_name in settings.TAXONOMY_MODELS:
        try:
            app = __import__(app_name)
            for model_name in [i.strip() for i in models_name.split(',')]:
                try:
                    model = getattr(app.models, model_name)
                except AttributeError:
                    continue
                models.append(model)
        except:
            continue
    # Retrieve the instance lists.
    for model in models:
        if hasattr(model, 'Taxonomy'):
            # Retrieve the sort order if any
            try:
                sortfields = getattr(model.Taxonomy, 'order_by')
            except AttributeError:
                sortfields = None
            # Go through all the fields
            try:
                fields = getattr(model.Taxonomy, 'fields')
                for field in fields:
                    if not hasattr(model, field) or not hasattr(model, 'objects'):
                        continue
                    qs_term = field + '__id'
                    obj_manager = getattr(model, 'objects')
                    if sortfields:
                        objs = obj_manager.filter(**{qs_term:term.id}).order_by(*sortfields)
                    else:
                        objs = obj_manager.filter(**{qs_term:term.id})
                    for obj in objs:
                        instances.append({
                            'instance':obj,
                            'data':get_model_instance_data(obj, term)
                        })
            except AttributeError:
                continue
    # Finally, if there is any duplicate in the list, remove them.
    instance_ids = []
    res = []
    for instance in instances:
        if instance.id in instance_ids:
            continue
        instance_ids.append(instance.id)
        res.append(instance)
    return res

################################################################################
# FUNCTION get_model_instance_taxonomy_field
################################################################################

def get_model_instance_taxonomy_field(instance, field):
    """
    return the value of an instance field, if it's a callable, the
    method will be called on the instance with no argument.

    @instance: the model instance
    @field: the Taxonomy subclass field to use in order to find the model
            attribute we're looking for.
    """
    try:
        attr = getattr(instance, getattr(instance.Taxonomy, field))
        if callable(attr):
            return attr()
        else:
            return attr
    except AttributeError:
        return None
    return None

################################################################################
# FUNCTION get_model_instance_data
################################################################################

def get_model_instance_data(instance, term):
    """
    return a dictionary with the model instance taxonomy data.

    The returned dictionnary is as follow:

    {
        'template_name': The template that can be used to render this instance
        'title': The title of the instance
        'description': The description of the instance
        'author': The author of the instance
        'changefreq': The change frequency of this instance
        'priority': The priority of this instance (in the sitemap)
        'pubdate': The publication date of this instance
        'url': The url of this instance
    }

    None of those key are guaranteed to be there, so you should not assume
    there presence.

    @instance: the instance to draw the data from.
    @term: the term that was used to reach this instance.
    """
    data = {}
    if hasattr(instance, 'Taxonomy'):
        # Retrieve template name if any
        template_name = get_model_instance_taxonomy_field(instance, 'template_name')
        if template_name:
            data['template_name'] = template_name
        # Retrieve title if any
        title = get_model_instance_taxonomy_field(instance, 'title')
        if title:
            data['title'] = title
        # Retrieve description if any
        description = get_model_instance_taxonomy_field(instance, 'description')
        if description:
            data['description'] = title
        # Retrieve author if any
        author = get_model_instance_taxonomy_field(instance, 'author')
        if author:
            data['author'] = author
        # Retrieve changefreq if any
        changefreq = get_model_instance_taxonomy_field(instance, 'changefreq')
        if changefreq:
            data['changefreq'] = changefreq
        # Retrieve priority if any
        priority = get_model_instance_taxonomy_field(instance, 'priority')
        if priority:
            data['priority'] = priority
        # Retrieve published date if any:
        pubdate = get_model_instance_taxonomy_field(instance, 'pubdate')
        if pubdate:
            data['pubdate'] = pubdate
        # Retrieve url if any
        data['url'] = get_model_instance_url(
            instance,
            term,
            slugify(data['title']) if 'title' in data else None
        )
    return data

################################################################################
# FUNCTION get_model_instance_template_name
################################################################################

def get_model_instance_template_name(instance):
    """
    return the instance template name, if any

    @instance: the instance
    """
    if hasattr(instance, 'Taxonomy'):
        # Retrieve template name if any
        if hasattr(instance.Taxonomy, 'template_name'):
            return getattr(instance.Taxonomy, 'template_name')
    return None

################################################################################
# FUNCTION get_model_instance_url
################################################################################

def get_model_instance_url(instance, term, slug):
    """
    Try to retrieve the url of the instance and returns the results.
    None is returned if nothing could be found.

    The following attempt are made:
        1. If instance defined a url field, try to grab the value
           of this field.
        2. If attempt 1 fails, compute that path as being:
            term absolute path + instance title slug (based on title field)
            This attempt is only made when render_as_instance is set to true.
        3. Try to grab the instance get_absolute_path value.
        4. If all else fails, returns None

    @instance: the instance to grab the url from
    @term: the term that was used to reach this instance
    @slug: the instance title slug
    """
    url = None
    if hasattr(instance, 'Taxonomy'):
        if hasattr(instance.Taxonomy, 'url'):
            field_name = getattr(instance.Taxonomy, 'url')
            if hasattr(instance, field_name):
                field = getattr(instance, field_name)
                if callable(field):
                    url = field()
                else:
                    url = field
    if not url and term.render_as_instance and hasattr(instance, 'Taxonomy') and slug:
        if hasattr(instance.Taxonomy, 'fields'):
            fields = getattr(instance.Taxonomy, 'fields')
            for field in fields:
                if hasattr(instance, field):
                    qterm = field + '__id'
                    objs = instance.__class__.objects.filter(**{qterm:term.id})
                    if len(objs) > 0:
                        url = term.get_absolute_path() + '/' + slug
    if not url and hasattr(instance, 'get_absolute_path'):
        get_url = getattr(instance, 'get_absolute_path')
        if callable(get_url):
            url = get_url()
    return url

